package aerolinea;

import java.math.BigDecimal;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import excepciones.NoPuedeComprarException;
import excepciones.NoPuedeReservarException;

import usuario.Usuario;
import utils.fecha.FormateadorSimple;
import vuelo.VueloConEscala;
import vuelo.VueloDirecto;
import vuelo.VueloDirectoComparator;
import asiento.Asiento;
import busqueda.Busqueda;


public abstract class Aerolinea {
	private BigDecimal porcentajeImpuesto;
	private FormateadorSimple formateador;
	private int diasDeVencimientoDeReserva;
	
	public String toString(){
		return this.getClass().toString().replaceAll("Adapter", "").substring(16);
	}
	
	public void comprar(Asiento asiento, Usuario usuario) throws NoPuedeComprarException{
		this.comprarAsiento(asiento, usuario);
		TodasLasAerolineas todasLasAerolineas = TodasLasAerolineas.getInstance();
		todasLasAerolineas.agregarCompraEnTabla(asiento);
	}
	
	public abstract void comprarAsiento(Asiento asiento, Usuario usuario) throws NoPuedeComprarException;
	
	public abstract void reservarAsiento(Asiento asiento, Usuario usuario) throws NoPuedeReservarException;
	
	public Collection<VueloDirecto> buscarVuelosDirectos(Busqueda busqueda) throws ParseException {
		return this.obtenerVuelosDirectosDisponibles(busqueda);	
	}

	public Collection<VueloConEscala> buscarVuelosConEscala(Busqueda busqueda) throws ParseException {
		return this.obtenerVuelosConEscalaDisponibles(busqueda);	
	}
	
	//obtiene los vuelos directos
	public Collection<VueloDirecto> obtenerVuelosDirectosDisponibles(Busqueda busqueda) throws ParseException{
		return this.pasarAsientosAVuelos(this.getAsientosDisponibles(busqueda));
	}
	
	//obtiene los vuelos con escalas.por ahora 2 escalas	
	public Collection<VueloConEscala> obtenerVuelosConEscalaDisponibles(Busqueda busqueda) throws ParseException {
		
		Collection<VueloConEscala> vuelosConEscala = new ArrayList<VueloConEscala>();
			
		Collection<VueloDirecto> vuelosDirectosSinDestino =this.obtenerVuelosDirectosSinDestino(busqueda); 
		
		for (VueloDirecto vueloDirecto : vuelosDirectosSinDestino) {
			vuelosConEscala.addAll(this.obtenerVueloConEscala(vueloDirecto, busqueda));
		}
		
		return vuelosConEscala;
	}
	
	//obtiene los vuelos iniciales para un vuelo en escala
	private Collection<VueloDirecto> obtenerVuelosDirectosSinDestino(Busqueda busqueda) throws ParseException{
		Busqueda busquedaSinDestino=busqueda.clone();
		busquedaSinDestino.setDestino(null);
		return obtenerVuelosDirectosDisponibles(busquedaSinDestino);
	}
	
	//obtiene la continuacion de vuelos para un vuelo directo,conformando un vuelo en escala
	private Collection<VueloConEscala> obtenerVueloConEscala(VueloDirecto vueloDirectoInicial,Busqueda busqueda) throws ParseException{
		Collection<VueloConEscala> vuelosConEscalas = new ArrayList<VueloConEscala>();
		
		Busqueda busquedaVueloContinuacion=busqueda.clone();
		busquedaVueloContinuacion.setOrigen(vueloDirectoInicial.getDestino());
		Date fechaConBrecha = new Date(vueloDirectoInicial.getFechaLLegada().getTime() + VueloConEscala.LIMITE_CONEXION);
		busquedaVueloContinuacion.setHoraSalida(getFormateador().convertirHora(fechaConBrecha));
	
		Collection<VueloDirecto> vuelosDirectosContinuaciones =this.obtenerVuelosDirectosDisponibles(busquedaVueloContinuacion);
		
		//si no es un camino muerto
		if(vuelosDirectosContinuaciones.size()>0){
			vuelosDirectosContinuaciones.add(vueloDirectoInicial);
			Collections.sort((List<VueloDirecto>)vuelosDirectosContinuaciones, new VueloDirectoComparator());
			VueloConEscala vueloConEscala = new VueloConEscala(vuelosDirectosContinuaciones);
			vuelosConEscalas.add(vueloConEscala);
		}
		return vuelosConEscalas;
	}
	
	//En base a una coleccion de asiento los ordena segun el codigo de vuelo y devuelve los vuelos con los asientos
	private Collection<VueloDirecto> pasarAsientosAVuelos(Collection<Asiento> asientosFormateados) {
		Collection<VueloDirecto> colVuelos = new ArrayList<VueloDirecto>();
		
		for(Asiento asiento : asientosFormateados){
			VueloDirecto miVuelo;
			if (this.existeUnVueloCreadoPara(colVuelos, asiento))
				miVuelo = this.buscarVueloCorrespondiente(asiento, colVuelos);
			else
				miVuelo = new VueloDirecto(this.extraerCodigoVuelo(asiento.getCodigoAsiento()),asiento);
			
			colVuelos.add(this.agregarAsientoAlVuelo(miVuelo, asiento));
		}
		
		return colVuelos;
	}
	//invoca a la aerolinea para obtener los asientos disponibles
	public abstract Collection<Asiento> getAsientosDisponibles(Busqueda busqueda) throws ParseException;
	
	private VueloDirecto buscarVueloCorrespondiente(Asiento asiento, Collection<VueloDirecto> colVuelos) {
		for(VueloDirecto vuelo : colVuelos){
			if(vuelo.getCodigoDeVuelo() == this.extraerCodigoVuelo(asiento.getCodigoAsiento())) return vuelo;
		}
		return null;
	}

	/*
	 *agrega el asiento al vuelo,lo cual implica ademas decirle al asiento a que vuelo pertenece y al vuelo
	 *a que aerolinea pertenece 
	 */
	private VueloDirecto agregarAsientoAlVuelo(VueloDirecto vuelo, Asiento asiento) {
		vuelo.addAsiento(asiento);
		asiento.setVuelo(vuelo);
		vuelo.setAerolinea(this);
		return vuelo;
	}


	private boolean existeUnVueloCreadoPara(Collection<VueloDirecto> colVuelos, Asiento asiento) {
		
		if (colVuelos.isEmpty()) return false;
		
		for(VueloDirecto vuelo : colVuelos){
			if (vuelo.getCodigoDeVuelo() == (this.extraerCodigoVuelo(asiento.getCodigoAsiento())))
				{
					return true;
				}
		}
		
		return false;
	}

	public abstract String extraerCodigoVuelo(String codigoAsiento);
	
	///////////////////////////////////////////////////////////////////////
	public void setPorcentajeImpuesto(BigDecimal porcentajeImpuesto) {
		this.porcentajeImpuesto = porcentajeImpuesto;
	}
	public BigDecimal getPorcentajeImpuesto() {
		return porcentajeImpuesto;
	}


	public FormateadorSimple getFormateador() {
		return formateador;
	}


	public void setFormateador(FormateadorSimple formateador) {
		this.formateador = formateador;
	}

	public void setDiasDeVencimientoDeReserva(int diasDeVencimientoDeReserva) {
		this.diasDeVencimientoDeReserva = diasDeVencimientoDeReserva;
	}

	public int getDiasDeVencimientoDeReserva() {
		return diasDeVencimientoDeReserva;
	}
	
	}
